namespace Microsoft.ServiceModel.Samples.Discovery
{
    using System;
    using System.Xml;
    using System.Xml.Serialization;
    using System.Collections.Generic;
    using System.Threading;
    using System.ServiceModel;
    using System.ServiceModel.Description;
    using Microsoft.ServiceModel.Samples.Discovery.Protocol;
    
    public class Announcer : IDisposable
    {
        long appSeqInstanceId;
        string appSeqSeqId;
        int appSeqMsgNum;

        OutputBinding outputBinding;
        ChannelFactory<IAnnouncementContract> factory;
        IAnnouncementContract channel;
        bool disposed;

        public Announcer()
            : this(ProtocolUtility.CreateMulticastOutputBinding())
        {
        }

        public Announcer(OutputBinding outputBinding)
        {
            Initialize(outputBinding);
        }

        void Initialize(OutputBinding outputBinding)
        {
            if (outputBinding == null)
                throw new ArgumentNullException("outputBinding");

            this.appSeqInstanceId = ProtocolUtility.CreateAppSequenceInstanceId();
            this.appSeqSeqId = Guid.NewGuid().ToString();
            this.appSeqMsgNum = 0;
            this.outputBinding = outputBinding;
            
            // create the channel factory for sending the announcement messages
            this.factory = new ChannelFactory<IAnnouncementContract>(
                this.outputBinding.Binding,
                new EndpointAddress(this.outputBinding.ToUri));

            // add the via behaviour so that messages addresses to the 
            // well known discovery address can be send to appropriate 
            // physical address.
            this.factory.Endpoint.Behaviors.Add(
                new ClientViaBehavior(this.outputBinding.ViaUri));

            // create channel using which to send the announcement messages
            this.channel = this.factory.CreateChannel();

            this.disposed = false;
        }

        public void SendHello(ServiceProperties serviceProperties)
        {
            if (serviceProperties == null)
                throw new ArgumentNullException("serviceProperties");

            SendHello(Hello.FromServiceProperties(serviceProperties));
        }

        public void SendBye(ServiceProperties serviceProperties)
        {
            if (serviceProperties == null)
                throw new ArgumentNullException("serviceProperties");

            SendBye(Bye.FromServiceProperties(serviceProperties));
        }

        void SendHello(Hello hello)
        {
            HelloMessage message = new HelloMessage();
            message.Hello = hello;
            message.AppSequence = CreateAppSequence();

            IClientChannel clientChannel = (IClientChannel)this.channel;
            using (new OperationContextScope(clientChannel))
            {
                OperationContext.Current.OutgoingMessageHeaders.MessageId = 
                    new UniqueId();
                   
                this.channel.HelloOperation(message);
            }
        }

        void SendBye(Bye bye)
        {
            ByeMessage message = new ByeMessage();
            message.Bye = bye;
            message.AppSequence = CreateAppSequence();

            IClientChannel clientChannel = (IClientChannel)this.channel;
            using (new OperationContextScope(clientChannel))
            {
                OperationContext.Current.OutgoingMessageHeaders.MessageId = 
                    new UniqueId();
                   
                this.channel.ByeOperation(message);
            }
        }

        AppSequence CreateAppSequence()
        {
            AppSequence appSeq = new AppSequence();
            appSeq.InstanceId = appSeqInstanceId;
            appSeq.SequenceId = this.appSeqSeqId;
            appSeq.MessageNumber = (uint)Interlocked.Increment(ref this.appSeqMsgNum);

            return appSeq;
        }

        #region IDisposable Members

        public void Dispose()
        {
            if (!this.disposed)
            {
                IClientChannel clientChannel = (IClientChannel)this.channel;
                if (clientChannel != null)
           
                {
                    clientChannel.Close();
                }

                if (this.factory != null)
                {
                    this.factory.Close();
                }

                this.disposed = true;
            }
        }

        #endregion
    }
}
